package cn.zhx.config;

import cn.zhx.types.annotations.DCCValue;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.CuratorCache;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

@Configuration
@Slf4j
public class DCCValueBeanFactory implements BeanPostProcessor {

    private static final String BASE_CONFIG_PATH = "/big-market-dcc";
    private static final String BASE_CONFIG_PATH_CONFIG = BASE_CONFIG_PATH + "/config";

    private final CuratorFramework client;
    private final Map<String, Object> dccObjGroup = new HashMap<>();


    /**
     * 构造函数，用于初始化 DCCValueBeanFactory(实例化bean先于postProcessAfterInitialization(初始化之后)执行)
     * 它会检查基础配置路径是否存在，如果不存在则创建
     * 并且会根据 CuratorFramework 客户端设置数据变更监听
     *
     * @param client CuratorFramework 客户端，用于与 ZooKeeper 交互
     * @throws Exception 如果创建基础配置路径失败，抛出异常
     */
    public DCCValueBeanFactory(CuratorFramework client) throws Exception {
        this.client = client;

        // 检查基础配置路径是否存在，不存在则创建
        if(null == client.checkExists().forPath(BASE_CONFIG_PATH_CONFIG)){
            client.create().creatingParentsIfNeeded().forPath(BASE_CONFIG_PATH_CONFIG);
            log.info("DCC 节点监听 base node {} not absent create new done!", BASE_CONFIG_PATH_CONFIG);
        }

        // 构建并启动 CuratorCache，用于监听 ZooKeeper 上的数据变化  类似于zookeeper中的节点？ZNode
        CuratorCache curatorCache = CuratorCache.build(client, BASE_CONFIG_PATH);
        curatorCache.start();

        // 注册监听器，当监听的节点发生变化时执行相应的逻辑
        //data = open / close
        curatorCache.listenable().addListener(((type, oldDate, data) -> {
            switch (type) {
                case NODE_CHANGED:
                    // 节点发生变化时的处理逻辑 data = open/close
                    String dccValuePath = data.getPath();
                    // dccValuePath = big-market-dcc/config/degradeSwitch
                    Object objBean = dccObjGroup.get(dccValuePath);
                    if (null == objBean) return;
                    try {
                        // 动态反射更新对象属性
                        // /big-market-dcc/config/degradeSwitch => 在postProcessAfterInitialization类中创建了对应的节点
                        //field = degradeSwitch
                        Field field = objBean.getClass().getDeclaredField(dccValuePath.substring(dccValuePath.lastIndexOf("/") + 1));
                        field.setAccessible(true);
                        field.set(objBean, new String(data.getData()));
                        field.setAccessible(false);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                    break;
                default:
                    break;
            }
        }));
    }


    /**
     * postProcessAfterInitialization bean(容器中所有的bean)初始化之后进行额外处理
     * postProcessAfterInitialization 的用途是获取扫描的 Bean 对象类，
     * 对这些类的属性判断是否存在添加了自定义注解的属性，存在则进行管理。
     * 这里面有一些小的逻辑，当一个值已经存在于配置中心，则在启动的时候把配置中心中的值赋值给应用中类的属性。
     * 另外如果是首次启动，则会把自定义注解中默认配置的值，赋值给属性。如 @DCCValue("degradeSwitch:open") 赋值 open 给属性。
     *
     * @param bean       正在被初始化的Bean对象
     * @param beanName   Bean的名称
     * @return           返回处理后的Bean对象
     * @throws BeansException 如果Bean处理过程中发生错误，则抛出该异常
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 获取bean的类信息
        Class<?> beanClass = bean.getClass();
        // 获取bean类中声明的所有字段
        Field[] fields = beanClass.getDeclaredFields();
        for (Field field : fields) {
            // 检查字段是否带有@DCCValue注解
            if (field.isAnnotationPresent(DCCValue.class)) {
                // 获取@DCCValue注解实例
                DCCValue dccValue = field.getAnnotation(DCCValue.class);

                // 获取注解中的值
                String value = dccValue.value();
                // 如果值为空，则抛出运行时异常
                if (StringUtils.isBlank(value)) {
                    throw new RuntimeException(field.getName() + " @DCCValue is not config value config case 「isSwitch/isSwitch:1」");
                }

                // 分割注解值为配置键和默认值   degradeSwitch:open
                String[] splits = value.split(":");
                String key = splits[0];
                String defaultValue = splits.length == 2 ? splits[1] : null;

                // 尝试设置字段值
                try {
                    // 拼接配置键的路径
                    String keyPath = BASE_CONFIG_PATH_CONFIG.concat("/").concat(key);
                    // 检查配置节点是否存在
                    if (null == client.checkExists().forPath(keyPath)) {
                        // 如果节点不存在，则创建节点并设置默认值
                        client.create().creatingParentsIfNeeded().forPath(keyPath);
                        if (StringUtils.isNotBlank(defaultValue)) {
                            // 设置字段为可访问
                            field.setAccessible(true);
                            // 设置字段默认值
                            field.set(bean, defaultValue);
                            // 恢复字段访问控制
                            field.setAccessible(false);
                        }
                        // 记录日志
                        log.info("DCC 节点监听 创建节点 {}", keyPath);
                    } else {
                        // 如果节点存在，获取节点的配置值
                        String configValue = new String(client.getData().forPath(keyPath));
                        // 如果配置值不为空，则设置字段值
                        if (StringUtils.isNotBlank(configValue)) {
                            // 设置字段为可访问
                            field.setAccessible(true);
                            // 设置字段值
                            field.set(bean, configValue);
                            // 恢复字段访问控制
                            field.setAccessible(false);
                            // 记录日志
                            log.info("DCC 节点监听 设置配置 {} {} {}", keyPath, field.getName(), configValue);
                        }
                    }
                } catch (Exception e) {
                    // 如果发生异常，抛出运行时异常
                    throw new RuntimeException(e);
                }

                // 将配置键和bean的映射存入map
                dccObjGroup.put(BASE_CONFIG_PATH_CONFIG.concat("/").concat(key), bean);
            }
        }
        // 返回处理后的bean
        return bean;
    }



}
